#!/usr/bin/env python3
"""
Simple mock backend to capture registration POSTs from the frontend.
Listens on port 8080 and handles CORS preflight. Logs the received JSON and
returns a JSON response with `insert_success`.

Usage:
  python mock_backend.py            # start on 0.0.0.0:8080
  python mock_backend.py --port 9000
  python mock_backend.py --fail    # return insert_success: false
"""

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import logging
import time


class MockHandler(BaseHTTPRequestHandler):
    def _set_cors_headers(self, status=200, extra_headers=None):
        self.send_response(status)
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        # Allow cross-origin so the browser can POST from another port/origin
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        if extra_headers:
            for k, v in extra_headers.items():
                self.send_header(k, v)
        self.end_headers()

    def do_OPTIONS(self):
        # Respond to preflight
        self._set_cors_headers()
        self.wfile.write(b"{}")

    def do_GET(self):
        if self.path == '/api/config':
            cfg = getattr(self.server, '_mock_config', {})
            self._set_cors_headers(200)
            self.wfile.write(json.dumps(cfg, ensure_ascii=False).encode('utf-8'))
            return

        if self.path == '/api/requests':
            reqs = getattr(self.server, '_received_requests', [])
            self._set_cors_headers(200)
            self.wfile.write(json.dumps(reqs, ensure_ascii=False).encode('utf-8'))
            return

        self.send_response(404)
        self.end_headers()

    def do_POST(self):
        # config endpoint
        if self.path == '/api/config':
            content_length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(content_length) if content_length else b''
            try:
                data = json.loads(body.decode('utf-8')) if body else {}
            except Exception as e:
                logging.exception('Invalid JSON for config')
                self._set_cors_headers(400)
                self.wfile.write(json.dumps({'ok': False, 'error': 'invalid_json', 'detail': str(e)}, ensure_ascii=False).encode('utf-8'))
                return

            # Acceptable fields: insert_success (bool), error (str), status (int), delay_ms (int), response_override (object)
            cfg = getattr(self.server, '_mock_config', {})
            cfg.update(data)
            self.server._mock_config = cfg
            self._set_cors_headers(200)
            self.wfile.write(json.dumps({'ok': True, 'config': cfg}, ensure_ascii=False).encode('utf-8'))
            return

        if self.path != '/api/request':
            self.send_response(404)
            self.end_headers()
            return

        content_length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(content_length) if content_length else b''
        # Print raw received body to terminal (use replace to avoid decode errors)
        try:
            print('\n--- Incoming POST (raw) ---')
            print(body.decode('utf-8', errors='replace'))
            print('--- end incoming ---\n', flush=True)
        except Exception:
            # Fallback: print bytes repr
            print('\n--- Incoming POST (raw bytes) ---')
            print(repr(body))
            print('--- end incoming ---\n', flush=True)
        try:
            data = json.loads(body.decode('utf-8')) if body else None
        except Exception as e:
            logging.exception('Failed to parse JSON')
            # Also print parse error and raw body to terminal
            print('\n--- JSON parse error ---')
            print('Error:', str(e))
            try:
                print('Raw body:', body.decode('utf-8', errors='replace'))
            except Exception:
                print('Raw body (bytes):', repr(body))
            print('--- end parse error ---\n', flush=True)

            self._set_cors_headers(400)
            resp = {'insert_success': False, 'error': 'invalid_json', 'detail': str(e)}
            # print response that will be sent
            try:
                print('\n--- Responding (error) ---')
                print(json.dumps(resp, ensure_ascii=False, indent=2))
                print('--- end response ---\n', flush=True)
            except Exception:
                print('\n--- Responding (error, unserializable) ---', resp, flush=True)

            self.wfile.write(json.dumps(resp, ensure_ascii=False).encode('utf-8'))
            return

        # store received request for inspection
        reqs = getattr(self.server, '_received_requests', [])
        reqs.append({'path': self.path, 'body': data, 'client': self.client_address[0], 'time': self.log_date_time_string()})
        # keep last 100
        self.server._received_requests = reqs[-100:]

        # Log received data (pretty printed)
        logging.info('Received POST %s\n%s', self.path, json.dumps(data, ensure_ascii=False, indent=2))

        # Special-case: respond to login/query requests with a simulated user record
        # This allows the frontend login flow to receive `query_success` and `data`.
        try:
            cfg = getattr(self.server, '_mock_config', {})
            if isinstance(data, dict) and data.get('operation') == 'query' and data.get('table') == 'user':
                # Try to determine username from condition or data
                username = None
                if isinstance(data.get('condition'), dict):
                    username = data['condition'].get('username')
                if not username and isinstance(data.get('data'), dict):
                    username = data['data'].get('username')

                # If users database provided in config, use that
                user_record = None
                users_cfg = cfg.get('users') if isinstance(cfg, dict) else None
                if users_cfg and username in users_cfg:
                    # allow config to provide full user object
                    user_record = users_cfg[username]
                else:
                    # fallback: echo back provided password_hash (so frontend can test success)
                    provided_hash = None
                    if isinstance(data.get('data'), dict):
                        provided_hash = data['data'].get('password_hash')
                    user_record = {
                        'user_id': f'user-{username}' if username else 'user-unknown',
                        'username': username,
                        'password_hash': provided_hash
                    }

                resp = {'query_success': True, 'data': user_record}
                # print simulated query response
                try:
                    print('\n--- Responding (simulated query) ---')
                    print(json.dumps(resp, ensure_ascii=False, indent=2))
                    print('--- end response ---\n', flush=True)
                except Exception:
                    print('\n--- Responding (simulated query, unserializable) ---', resp, flush=True)

                self._set_cors_headers(200)
                self.wfile.write(json.dumps(resp, ensure_ascii=False).encode('utf-8'))
                return
        except Exception:
            logging.exception('Error handling simulated login response')

        # Build response according to configuration
        cfg = getattr(self.server, '_mock_config', {})
        # optional delay
        delay_ms = int(cfg.get('delay_ms', 0) or 0)
        if delay_ms > 0:
            logging.info('Delaying response by %d ms', delay_ms)
            time.sleep(delay_ms / 1000.0)

        # response override: return this object as-is
        if 'response_override' in cfg:
            status = int(cfg.get('status', 200) or 200)
            # log response override before sending
            try:
                logging.info('Responding with (override)\n%s', json.dumps(cfg['response_override'], ensure_ascii=False, indent=2))
            except Exception:
                logging.info('Responding with override (unserializable)')

            # print override response to terminal
            try:
                print('\n--- Responding (override) ---')
                print(json.dumps(cfg['response_override'], ensure_ascii=False, indent=2))
                print('--- end response ---\n', flush=True)
            except Exception:
                print('\n--- Responding (override, unserializable) ---', cfg.get('response_override'), flush=True)

            self._set_cors_headers(status)
            self.wfile.write(json.dumps(cfg['response_override'], ensure_ascii=False).encode('utf-8'))
            return

        # default behavior: use insert_success / error / status
        insert_success = cfg.get('insert_success', True)
        error_msg = cfg.get('error')
        status = int(cfg.get('status', 200) or 200)

        resp = {'insert_success': bool(insert_success), 'received': data}
        if error_msg and not insert_success:
            resp['error'] = error_msg

        # Log the response body that will be sent
        try:
            logging.info('Responding with\n%s', json.dumps(resp, ensure_ascii=False, indent=2))
        except Exception:
            logging.info('Responding with (unserializable)')

        # Also print response to terminal
        try:
            print('\n--- Responding (default) ---')
            print(json.dumps(resp, ensure_ascii=False, indent=2))
            print('--- end response ---\n', flush=True)
        except Exception:
            print('\n--- Responding (default, unserializable) ---', resp, flush=True)

        self._set_cors_headers(status)
        self.wfile.write(json.dumps(resp, ensure_ascii=False).encode('utf-8'))

    def log_message(self, format, *args):
        # Redirect BaseHTTPRequestHandler logs through logging module
        logging.info('%s - - [%s] %s', self.client_address[0], self.log_date_time_string(), format % args)


def run_server(host: str, port: int):
    server_address = (host, port)
    httpd = HTTPServer(server_address, MockHandler)
    # default config
    httpd._mock_config = {'insert_success': True}
    httpd._received_requests = []
    logging.info('Starting mock backend on http://%s:%s', host if host else '0.0.0.0', port)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        logging.info('Shutting down mock backend')
        httpd.server_close()


if __name__ == '__main__':
    # Simplified startup for debugging: always bind to 0.0.0.0:8080
    logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')
    run_server('0.0.0.0', 8080)
